
import 'dart:convert';
import 'dart:ffi' as ffi;
import 'dart:isolate';

import 'package:ffi/ffi.dart';
import 'package:tencent_rtc/bindings/trtc_cloud_struct.dart';
import 'package:tencent_rtc/impl/trtc_cloud_listener_prase.dart';
import 'package:tencent_rtc/trtc_cloud_listener.dart';

import 'package:tencent_rtc/tx_audio_effect_manager.dart';
import '../tx_device_manager.dart';
import 'ffi_bindings.dart'as native;
import 'load_dynamic_lib.dart';

class TRTCCloudCallbackType {
  static const  int _log = 0;

  static const  int _video_custom_process = 1;

  static const  int _local_video_render = 2;

  static const  int _remote_video_render = 3;

  static const  int _audio_frame = 4;

  static const  int _music_preload = 5;

  static const  int _music_play = 6;

  static const  int _device_change = 7;
}

abstract class ListenerNative<T> {
  static bool _isInit = false;

  final ListenerParse<T> _listeners;
  final ffi.Pointer<ffi.Void> _instanceNativePointer;

  late native.FFIBindings _listenerFFIBindings;
  late ReceivePort _receivePort;


  ListenerNative(
      this._listeners,
      this._instanceNativePointer,
      ) {
    _listenerFFIBindings = native.FFIBindings(LoadDynamicLib().loadTRTCPluginAdapter());

    if (!ListenerNative._isInit) {
      try {
        _listenerFFIBindings.InitDartApiDL(ffi.NativeApi.initializeApiDLData);
        ListenerNative._isInit = true;
      } catch (e) {
        print(e);
      }
    }

    _receivePort = ReceivePort();
    _receivePort.listen(_receiveNativePortData);
  }

  void addListener(T listener) {
    _listeners.addListener(listener);
  }

  void removeListener(T listener) {
    _listeners.removeListener(listener);
  }

  void clearListeners() {
    _listeners.clearListeners();
  }

  void unRegisterNativeListener() {
    _listeners.clearListeners();
    _receivePort.close();
  }

  _receiveNativePortData(var message);
}

class TRTCCloudListenerNative extends ListenerNative<TRTCCloudListener> {

  TRTCCloudListenerNative(trtc_cloud instanceNativePointer)
      : super(TRTCCloudListenerParse(), instanceNativePointer) {
    _listenerFFIBindings.registerListener(_receivePort.sendPort.nativePort, _instanceNativePointer);
  }

  @override
  void _receiveNativePortData(var message) {
    var arguments = jsonDecode(message);
    String typeStr = arguments['type'];
    var params = arguments['params'];
    params ??= <String, dynamic>{};

    _listeners.handleListener(typeStr, params);
  }

  @override
  void unRegisterNativeListener() {
    super.unRegisterNativeListener();
    _listenerFFIBindings.unRegisterListener(_instanceNativePointer);
  }
}

class TRTCAudioFrameCallbackNative extends ListenerNative<TRTCAudioFrameCallback> {
  TRTCAudioFrameCallbackNative(trtc_cloud instanceNativePointer)
      : super(TRTCAudioFrameCallbackParse(), instanceNativePointer) {
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._audio_frame,
        ffi.nullptr,
        true);
  }

  @override
  _receiveNativePortData(message) {
    var arguments = jsonDecode(message);
    String typeStr = arguments['type'];
    var params = arguments['params'];

    _listeners.handleListener(typeStr, params);
  }

  @override
  void unRegisterNativeListener() {
    super.unRegisterNativeListener();
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._audio_frame,
        ffi.nullptr,
        false);
  }
}

class TRTCLogCallbackNative extends ListenerNative<TRTCLogCallback> {
  TRTCLogCallbackNative(trtc_cloud instanceNativePointer)
      : super(TRTCLogCallbackParse(), instanceNativePointer) {
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._log,
        ffi.nullptr,
        true);
  }

  @override
  _receiveNativePortData(message) {
    var arguments = jsonDecode(message);
    String typeStr = arguments['type'];
    var params = arguments['params'];

    _listeners.handleListener(typeStr, params);
  }

  @override
  void unRegisterNativeListener() {
    super.unRegisterNativeListener();
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._log,
        ffi.nullptr,
        false);
  }
}

class TXMusicPreloadObserverNative extends ListenerNative<TXMusicPreloadObserver> {
  TXMusicPreloadObserverNative(tx_audio_effect_manager instanceNativePointer)
      : super(TXMusicPreloadObserverParse(), instanceNativePointer) {
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._music_preload,
        ffi.nullptr,
        true);
  }

  @override
  _receiveNativePortData(message) {
    var arguments = jsonDecode(message);
    String typeStr = arguments['type'];
    var params = arguments['params'];

    _listeners.handleListener(typeStr, params);
  }

  @override
  void unRegisterNativeListener() {
    super.unRegisterNativeListener();
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._music_preload,
        ffi.nullptr,
        false);
  }
}

class TXDeviceObserverNative extends ListenerNative<TXDeviceObserver> {
  TXDeviceObserverNative(tx_device_manager instanceNativePointer)
      : super(TXDeviceObserverParse(), instanceNativePointer) {
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._device_change,
        ffi.nullptr,
        true);
  }

  @override
  _receiveNativePortData(message) {
    var arguments = jsonDecode(message);
    String typeStr = arguments['type'];
    var params = arguments['params'];

    _listeners.handleListener(typeStr, params);
  }

  @override
  void unRegisterNativeListener() {
    super.unRegisterNativeListener();
    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._device_change,
        ffi.nullptr,
        false);
  }
}


class TXMusicPlayObserverNative {
  static bool _isInit = false;

  final TXMusicPlayObserverParse observerParse = TXMusicPlayObserverParse();
  final ffi.Pointer<ffi.Void> _instanceNativePointer;

  late native.FFIBindings _listenerFFIBindings;
  late ReceivePort _receivePort;

  TXMusicPlayObserverNative(this._instanceNativePointer) {
    _listenerFFIBindings = native.FFIBindings(LoadDynamicLib().loadTRTCPluginAdapter());

    if (!TXMusicPlayObserverNative._isInit) {
      try {
        _listenerFFIBindings.InitDartApiDL(ffi.NativeApi.initializeApiDLData);
        TXMusicPlayObserverNative._isInit = true;
      } catch (e) {
        print(e);
      }
    }

    _receivePort = ReceivePort();
    _receivePort.listen(_receiveNativePortData);
  }

  void _receiveNativePortData(var message) {
    var arguments = jsonDecode(message);
    String typeStr = arguments['type'];
    var params = arguments['params'];

    observerParse.handleListener(typeStr, params);
  }

  _setMusicCallbackNative(int musicId, bool enable) {
    Map<String, dynamic> params = {
      'musicId': musicId,
    };
    String jsonString = jsonEncode(params);

    _listenerFFIBindings.setCallback(
        _receivePort.sendPort.nativePort,
        _instanceNativePointer,
        TRTCCloudCallbackType._music_play,
        jsonString.toNativeUtf8().cast<ffi.Char>(),
        enable
    );
  }

  void addMusicObserver(int musicId, TXMusicPlayObserver observer) {
    observerParse.listeners[musicId] = observer;

    _setMusicCallbackNative(musicId, true);
  }

  void removeMusicObserver(int musicId) {
    TXMusicPlayObserver? observer = observerParse.listeners[musicId];
    observerParse.listeners.remove(musicId);

    if (observer != null && !observerParse.listeners.containsValue(observer)) {
      _setMusicCallbackNative(musicId, false);
    }
  }

  void unRegisterNativeListener() {
    observerParse.listeners.forEach((musicId, observer) {
      _setMusicCallbackNative(musicId, false);
    });
    observerParse.listeners.clear();
  }
}